# 导入核心库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import os
import warnings
warnings.filterwarnings('ignore')  # 屏蔽无关警告，提升可读性
# 数据库连接库
import pymysql
from sqlalchemy import create_engine
# 中文显示配置（固定有效配置，统一格式）
plt.rcParams["font.family"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['font.size'] = 10  # 统一图表字体大小
plt.rcParams['figure.dpi'] = 100  # 统一图表分辨率

# 全局配置
class Config:
    """全局配置类：集中管理数据库、路径、业务映射参数"""
    # 数据库配置（敏感信息占位符替换）
    DB = {
        "host": "[数据库地址]",
        "user": "[数据库用户名]",
        "password": "******",  # 密码脱敏
        "db": "lendingclub_loan_db_v2",
        "port": 3306,
        "table": "loan_fraud_2018_q1",
        "charset": "utf8mb4"  # 兼容特殊字符
    }
    
    # 输出路径（相对路径占位符，适配不同环境）
    OUTPUT = {
        "root": "[输出根路径]",
        "fig": "[图表输出路径]",  # 可视化图表
        "data": "[数据输出路径]"     # 处理后数据
    }  
        
    # 业务映射（补充注释，明确风险含义）
    GRADE_MAP = {"A": 1, "B": 2, "C": 3, "D": 4, "E": 5, "F": 6, "G": 7}  # 信用等级：A→低风险，G→高风险
    SUB_GRADE_MAP = {}
    # 生成细分信用等级映射（A1=1→低风险，G5=35→高风险）
    for idx, grade in enumerate(GRADE_MAP.keys()):
        for sub_idx in range(1, 6):
            SUB_GRADE_MAP[f"{grade}{sub_idx}"] = idx * 5 + sub_idx

# 工具函数：创建输出目录（独立函数，提升复用性）
def create_output_dirs():
    """创建输出目录（不存在则自动创建，规范日志输出）"""
    print("="*60)
    print("📂 初始化输出目录")
    print("="*60)
    for dir_name, dir_path in Config.OUTPUT.items():
        if not os.path.exists(dir_path):
            os.makedirs(dir_path)
            print(f"  ✅ 新建{dir_name}目录：{dir_path}")
        else:
            print(f"  ℹ️  {dir_name}目录已存在：{dir_path}")

# 执行目录创建
create_output_dirs()

# -------------------------- 1. 数据读取：从MySQL读取原始数据（完善异常处理） --------------------------
def read_mysql_data() -> pd.DataFrame:
    """
    从MySQL数据库读取信贷数据
    返回：
        pd.DataFrame: 读取成功的数据
    异常处理：
        连接失败、SQL语法错误、字段缺失等均打印详细信息并返回None
    """
    print("\n" + "="*60)
    print("🔌 从MySQL读取数据")
    print("="*60)
    try:
        # 创建数据库引擎（规范字符串格式）
        engine = create_engine(
            f"mysql+pymysql://{Config.DB['user']}:{Config.DB['password']}@{Config.DB['host']}:"
            f"{Config.DB['port']}/{Config.DB['db']}?charset={Config.DB['charset']}"
        )
        
        # 读取SQL（字段换行排列，提升可读性）
        sql = f"""
            SELECT id, loan_amnt, term, installment, grade, sub_grade, emp_length, 
                   home_ownership, annual_inc, verification_status, issue_d, loan_status, 
                   purpose, zip_code, addr_state, dti, delinq_2yrs, earliest_cr_line, 
                   fico_range_low, fico_range_high, inq_last_6mths, open_acc, pub_rec, 
                   revol_bal, revol_util, collections_12_mths_ex_med, application_type, 
                   delinq_amnt, pub_rec_bankruptcies, disbursement_method, debt_settlement_flag 
            FROM {Config.DB['table']}
        """
        
        # 执行读取
        df = pd.read_sql(sql, engine)
        print(f"  ✅ 数据读取成功：{df.shape[0]:,}行 × {df.shape[1]}列")
        print(f"  🔑 主键id非空数量：{df['id'].notna().sum():,}（总样本{df.shape[0]:,}）")
        return df
    
    except Exception as e:
        error_msg = str(e).strip()
        # 针对性提示错误原因
        if "Unknown database" in error_msg:
            print(f"  ❌ 错误：数据库{Config.DB['db']}不存在，请检查数据库配置")
        elif "Table doesn't exist" in error_msg:
            print(f"  ❌ 错误：表{Config.DB['table']}不存在，请检查表名")
        else:
            print(f"  ❌ 数据读取失败：{error_msg}")
        return None

# 执行数据读取（失败则终止流程）
df_raw = read_mysql_data()
if df_raw is None:
    raise SystemExit("❌ 数据读取中断，请先解决上述数据库问题")

# -------------------------- 2. 数据深度处理：缺失值填充+异常值修正 --------------------------
def process_raw_data(df: pd.DataFrame) -> pd.DataFrame:
    """
    原始数据预处理：按字段类型填充缺失值、按行业标准修正异常值
    参数：
        df: 原始数据DataFrame
    返回：
        pd.DataFrame: 处理后的数据（无缺失、无异常）
    """
    df_processed = df.copy()  # 复制数据，避免修改原始数据
    print("\n" + "="*60)
    print("📊 原始数据预处理（缺失值填充+异常值修正）")
    print("="*60)
    # 2.1 缺失值填充（按字段类型分类处理，规范日志）
    print("\n【第一步：缺失值填充】")
    # 数值型字段：中位数填充（抗极端值，适合金融数据）
    num_cols = [
        "loan_amnt", "installment", "annual_inc", "dti", "delinq_2yrs", 
        "fico_range_low", "fico_range_high", "inq_last_6mths", "open_acc", 
        "pub_rec", "revol_bal", "revol_util", "delinq_amnt", "pub_rec_bankruptcies"
    ]
    missing_num_cols = [col for col in num_cols if df_processed[col].isna().sum() > 0]
    
    if missing_num_cols:
        for col in missing_num_cols:
            missing_count = df_processed[col].isna().sum()
            median_val = df_processed[col].median()
            df_processed[col] = df_processed[col].fillna(median_val)  # 避免inplace警告
            print(f"  • {col:20}：填充{missing_count:,}条（中位数={median_val:.2f}）")
    else:
        print("  • 数值型字段无缺失值，无需填充")
    
    # 分类型字段：按业务风险逻辑填充（低风险优先）
    cat_fill_map = {
        "term": "36",                       # 借款期限：默认短期（风险低）
        "grade": "C",                       # 信用等级：默认中等（风险中）
        "sub_grade": "C3",                  # 细分等级：默认中等
        "home_ownership": "RENT",           # 住房类型：默认租赁（风险中）
        "verification_status": "Verified",  # 收入验证：默认已验证（风险低）
        "purpose": "debt_consolidation",    # 借款用途：默认债务整合（高频场景）
        "zip_code": "000",                  # 邮编：默认占位（无实际意义）
        "addr_state": "CA",                 # 州：默认CA（高频样本，风险中）
        "application_type": "Individual",   # 申请类型：默认个人（风险低）
        "disbursement_method": "DirectPay", # 放款方式：默认直接支付（风险低）
        "debt_settlement_flag": "N",        # 债务清算：默认无（风险低）
        "earliest_cr_line": "2000-01-01"    # 首次信用记录：默认2000年（中等时长）
    }
    missing_cat_cols = [col for col in cat_fill_map.keys() if df_processed[col].isna().sum() > 0]
    
    if missing_cat_cols:
        for col in missing_cat_cols:
            missing_count = df_processed[col].isna().sum()
            fill_val = cat_fill_map[col]
            df_processed[col] = df_processed[col].fillna(fill_val)  # 避免inplace警告
            print(f"  • {col:20}：填充{missing_count:,}条（默认值={fill_val}）")
    else:
        print("  • 分类型字段无缺失值，无需填充")
    
    # 2.2 日期字段强制转换（解决.dt访问器报错）
    print("\n【第二步：日期字段处理】")
    print(f"  • 转换前earliest_cr_line类型：{df_processed['earliest_cr_line'].dtype}")
    
    # 强制转换为datetime，兼容多种格式，失败设为NaT
    df_processed["earliest_cr_line"] = pd.to_datetime(
        df_processed["earliest_cr_line"],
        format=None,  # 自动识别格式
        errors="coerce"  # 转换失败设为NaT
    )
    
    # 统计转换结果并填充失败值
    convert_success = df_processed["earliest_cr_line"].notna().sum()
    convert_fail = df_processed["earliest_cr_line"].isna().sum()
    print(f"  • 转换结果：成功{convert_success:,}条 | 失败{convert_fail:,}条")
    
    if convert_fail > 0:
        default_date = pd.Timestamp("2000-01-01")
        df_processed["earliest_cr_line"] = df_processed["earliest_cr_line"].fillna(default_date)
        print(f"  • 填充{convert_fail:,}条失败值：默认日期{default_date.strftime('%Y-%m-%d')}")
    print(f"  • 转换后earliest_cr_line类型：{df_processed['earliest_cr_line'].dtype}（符合要求）")
    
    # 2.3 异常值修正（按金融行业标准，统计修正数量）
    print("\n【第三步：异常值修正】")
    abnormal_fix_count = 0  # 统计总修正数量
    # （1）借款金额：下限≥1（避免除零错误）
    before_fix = (df_processed["loan_amnt"] < 1).sum()
    if before_fix > 0:
        df_processed["loan_amnt"] = df_processed["loan_amnt"].clip(lower=1)
        abnormal_fix_count += before_fix
        print(f"  • loan_amnt(借款金额)：修正{before_fix:,}条<1的值（下限=1）")
    # （2）负债收入比：上限≤100%（负债不可能超过收入）
    before_fix = (df_processed["dti"] > 100).sum()
    if before_fix > 0:
        df_processed["dti"] = df_processed["dti"].clip(upper=100)
        abnormal_fix_count += before_fix
        print(f"  • dti(负债收入比)：修正{before_fix:,}条>100%的值（上限=100%）")
    # （3）循环信用使用率：上限≤100%（使用率无超100%的可能）
    before_fix = (df_processed["revol_util"] > 100).sum()
    if before_fix > 0:
        df_processed["revol_util"] = df_processed["revol_util"].clip(upper=100)
        abnormal_fix_count += before_fix
        print(f"  • revol_util(信用使用率)：修正{before_fix:,}条>100%的值（上限=100%）")
    # （4）年收入：IQR盖帽法（避免极端高收入影响）
    q1, q3 = df_processed["annual_inc"].quantile([0.25, 0.75])
    upper_limit = q3 + 1.5 * (q3 - q1)
    before_fix = (df_processed["annual_inc"] > upper_limit).sum()
    if before_fix > 0:
        df_processed["annual_inc"] = df_processed["annual_inc"].clip(upper=upper_limit)
        abnormal_fix_count += before_fix
        print(f"  • annual_inc(年收入)：修正{before_fix:,}条极端值（上限={upper_limit:,.0f}）")
    # （5）月供：下限≥1（月供无意义值修正）
    before_fix = (df_processed["installment"] < 1).sum()
    if before_fix > 0:
        df_processed["installment"] = df_processed["installment"].clip(lower=1)
        abnormal_fix_count += before_fix
        print(f"  • installment(月供)：修正{before_fix:,}条<1的值（下限=1）")
    print(f"\n  ✅ 数据预处理完成：缺失值全填充，异常值共修正{abnormal_fix_count:,}条")
    return df_processed

# 执行数据处理
df_processed = process_raw_data(df_raw)

# -------------------------- 3. 特征工程：构建反欺诈风险特征 --------------------------
def build_fraud_features(df: pd.DataFrame) -> pd.DataFrame:
    """
    反欺诈特征工程：基于业务逻辑构建信用、还款能力、行为、地域等风险特征
    参数：
        df: 预处理后的数据
    返回：
        pd.DataFrame: 含新增风险特征的数据
    """
    df_feature = df.copy()
    original_cols_count = df_feature.shape[1]  # 原始列数（用于统计新增特征数量）
    print("\n" + "="*60)
    print("🔧 反欺诈特征工程")
    print("="*60)
    # 3.1 信用相关特征（核心风险维度）
    print("\n【第一类：信用风险特征】")
    # 信用等级编码
    df_feature["grade_encoded"] = df_feature["grade"].map(Config.GRADE_MAP)
    # 细分信用等级编码（异常值填充为中等风险）
    df_feature["sub_grade_encoded"] = df_feature["sub_grade"].map(Config.SUB_GRADE_MAP)
    sub_grade_nan = df_feature["sub_grade_encoded"].isna().sum()
    if sub_grade_nan > 0:
        df_feature["sub_grade_encoded"] = df_feature["sub_grade_encoded"].fillna(18)  # C3对应值，中等风险
        print(f"  • sub_grade_encoded：{sub_grade_nan:,}条异常值填充为18（中等风险）")
    else:
        print(f"  • sub_grade_encoded：无异常值，编码完成")
    
    # FICO信用分（取高低区间均值）
    df_feature["fico_score"] = (df_feature["fico_range_low"] + df_feature["fico_range_high"]) / 2
    print(f"  • fico_score：FICO信用分均值计算完成")
    
    # 破产记录标记（>0→高风险）
    df_feature["has_bankruptcy"] = (df_feature["pub_rec_bankruptcies"] > 0).astype(int)
    bankruptcy_count = df_feature["has_bankruptcy"].sum()
    print(f"  • has_bankruptcy：{bankruptcy_count:,}条标记为高风险（占比{bankruptcy_count/len(df_feature)*100:.1f}%）")
    
    # 3.2 还款能力特征（核心风险维度）
    print("\n【第二类：还款能力特征】")
    # 收入-借款比（<2→高风险：收入覆盖借款能力弱）
    df_feature["income_loan_ratio"] = df_feature["annual_inc"] / df_feature["loan_amnt"]
    df_feature["is_low_income_loan"] = (df_feature["income_loan_ratio"] < 2).astype(int)
    low_income_count = df_feature["is_low_income_loan"].sum()
    print(f"  • is_low_income_loan：{low_income_count:,}条标记为高风险（收入-借款比<2，占比{low_income_count/len(df_feature)*100:.1f}%）")
    
    # 月供-收入比（>20%→高风险：月供压力大）
    df_feature["installment_income_ratio"] = df_feature["installment"] / (df_feature["annual_inc"] / 12)
    df_feature["is_high_installment"] = (df_feature["installment_income_ratio"] > 0.2).astype(int)
    high_inst_count = df_feature["is_high_installment"].sum()
    print(f"  • is_high_installment：{high_inst_count:,}条标记为高风险（月供-收入比>20%，占比{high_inst_count/len(df_feature)*100:.1f}%）")
    
    # 负债比风险（>36%→高风险：行业标准阈值）
    df_feature["is_high_dti"] = (df_feature["dti"] > 36).astype(int)
    high_dti_count = df_feature["is_high_dti"].sum()
    print(f"  • is_high_dti：{high_dti_count:,}条标记为高风险（负债收入比>36%，占比{high_dti_count/len(df_feature)*100:.1f}%）")
    
    # 3.3 借款行为特征（欺诈行为识别）
    print("\n【第三类：借款行为特征】")
    # 长期借款标记（60个月→高风险：还款周期长，违约概率高）
    df_feature["is_long_term"] = (df_feature["term"] == "60").astype(int)
    long_term_count = df_feature["is_long_term"].sum()
    print(f"  • is_long_term：{long_term_count:,}条标记为高风险（60个月借款，占比{long_term_count/len(df_feature)*100:.1f}%）")
    
    # 借款用途one-hot编码（保留高频用途，避免维度爆炸）
    purpose_dummies = pd.get_dummies(df_feature["purpose"], prefix="purpose", drop_first=True)
    df_feature = pd.concat([df_feature, purpose_dummies], axis=1)
    print(f"  • 借款用途编码：生成{purpose_dummies.shape[1]}列one-hot特征")
    
    # 邮编批量申请风险（团伙欺诈识别：95%分位数为阈值）
    # 提取邮编前3位（不足3位填充000）
    df_feature["zip_prefix"] = df_feature["zip_code"].str[:3].fillna("000")
    # 统计每个邮编的申请次数
    df_feature["zip_apply_count"] = df_feature.groupby("zip_prefix")["id"].transform("count")
    # 计算95%分位数阈值
    zip_95_quantile = df_feature["zip_apply_count"].quantile(0.95)
    batch_zip_threshold = int(zip_95_quantile)
    # 标记批量申请风险
    df_feature["is_batch_zip"] = (df_feature["zip_apply_count"] > batch_zip_threshold).astype(int)
    batch_zip_count = df_feature["is_batch_zip"].sum()
    print(f"  • is_batch_zip：{batch_zip_count:,}条标记为团伙风险（> {batch_zip_threshold}次/邮编，占比{batch_zip_count/len(df_feature)*100:.1f}%）")
    
    # 3.4 其他风险特征（地域、申请类型、债务状态）
    print("\n【第四类：其他风险特征】")
    # 高风险州标记（前10%风险率的州）
    state_risk_rate = df_feature.groupby("addr_state")["loan_status"].mean()
    high_risk_state_thresh = state_risk_rate.quantile(0.9)
    df_feature["is_high_risk_state"] = df_feature["addr_state"].apply(
        lambda x: 1 if state_risk_rate[x] >= high_risk_state_thresh else 0
    )
    high_state_count = df_feature["is_high_risk_state"].sum()
    print(f"  • is_high_risk_state：{high_state_count:,}条标记为高风险州（前10%风险率，占比{high_state_count/len(df_feature)*100:.1f}%）")
    
    # 联合申请标记（Joint App→高风险：责任分散）
    df_feature["is_joint_app"] = (df_feature["application_type"] == 1).astype(int)
    joint_app_count = df_feature["is_joint_app"].sum()
    print(f"  • is_joint_app：{joint_app_count:,}条标记为高风险（联合申请，占比{joint_app_count/len(df_feature)*100:.1f}%）")
    
    # 现金放款标记（Cash→高风险：资金流向难追踪）
    df_feature["is_cash_disburse"] = (df_feature["disbursement_method"] == 1).astype(int)
    cash_disburse_count = df_feature["is_cash_disburse"].sum()
    print(f"  • is_cash_disburse：{cash_disburse_count:,}条标记为高风险（现金放款，占比{cash_disburse_count/len(df_feature)*100:.1f}%）")
    
    # 收入未验证/债务清算/催收记录标记
    df_feature["is_income_unverified"] = (df_feature["verification_status"] == "Not Verified").astype(int)
    df_feature["has_debt_settlement"] = (df_feature["debt_settlement_flag"] == 1).astype(int)
    df_feature["has_collection"] = (df_feature["collections_12_mths_ex_med"] > 0).astype(int)
    
    # 统计这类特征的高风险数量
    unverified_count = df_feature["is_income_unverified"].sum()
    debt_settle_count = df_feature["has_debt_settlement"].sum()
    collection_count = df_feature["has_collection"].sum()
    print(f"  • is_income_unverified：{unverified_count:,}条高风险（收入未验证，占比{unverified_count/len(df_feature)*100:.1f}%）")
    print(f"  • has_debt_settlement：{debt_settle_count:,}条高风险（债务清算，占比{debt_settle_count/len(df_feature)*100:.1f}%）")
    print(f"  • has_collection：{collection_count:,}条高风险（催收记录，占比{collection_count/len(df_feature)*100:.1f}%）")
    
    # 3.5 信用历史时长特征（短信用历史→高风险）
    df_feature["earliest_cr_year"] = df_feature["earliest_cr_line"].dt.year 
    df_feature["credit_history_years"] = 2018 - df_feature["earliest_cr_year"]
    df_feature["is_short_credit"] = (df_feature["credit_history_years"] < 3).astype(int)
    short_credit_count = df_feature["is_short_credit"].sum()
    print(f"  • is_short_credit：{short_credit_count:,}条标记为高风险（信用历史<3年，占比{short_credit_count/len(df_feature)*100:.1f}%）")
    
    # 特征工程总结
    new_cols_count = df_feature.shape[1] - original_cols_count
    print(f"\n  ✅ 特征工程完成：新增{new_cols_count}个风险特征（含15个核心风险标记+{purpose_dummies.shape[1]}个用途编码）")
    return df_feature

# 执行特征工程
df_final = build_fraud_features(df_processed)

# -------------------------- 4. 探索性数据分析（EDA）：核心风险洞察 --------------------------
def fraud_eda_analysis(df: pd.DataFrame):
    """
    反欺诈EDA分析：输出核心风险洞察，生成可视化图表
    参数：
        df: 含风险特征的数据
    """
    print("\n" + "="*60)
    print("📈 反欺诈探索性数据分析（核心风险洞察）")
    print("="*60)
    # 4.1 基础风险分布
    total_samples = len(df)
    total_high_risk = df["loan_status"].sum()
    total_normal = total_samples - total_high_risk
    print(f"【基础风险分布】")
    print(f"  • 总样本：{total_samples:,}条")
    print(f"  • 高风险：{total_high_risk:,}条（占比{total_high_risk/total_samples*100:.1f}%）")
    print(f"  • 正常样本：{total_normal:,}条（占比{total_normal/total_samples*100:.1f}%）")
    
    # 4.2 核心特征与风险关联（中位数对比）
    print("\n【核心特征风险关联分析】")
    # 信用特征对比
    risk_fico = df[df['loan_status']==1]['fico_score'].median()
    normal_fico = df[df['loan_status']==0]['fico_score'].median()
    risk_grade = df[df['loan_status']==1]['grade_encoded'].median()
    normal_grade = df[df['loan_status']==0]['grade_encoded'].median()
    risk_grade_label = [k for k,v in Config.GRADE_MAP.items() if v==risk_grade][0]
    normal_grade_label = [k for k,v in Config.GRADE_MAP.items() if v==normal_grade][0]
    
    print(f"  • FICO信用分：高风险{risk_fico:.1f} | 正常{normal_fico:.1f}（差值{normal_fico-risk_fico:.1f}）")
    print(f"  • 信用等级：高风险{risk_grade}（{risk_grade_label}） | 正常{normal_grade}（{normal_grade_label}）")
    
    # 还款能力特征对比
    risk_income_loan = df[df['loan_status']==1]['income_loan_ratio'].median()
    normal_income_loan = df[df['loan_status']==0]['income_loan_ratio'].median()
    risk_inst_income = df[df['loan_status']==1]['installment_income_ratio'].median()*100
    normal_inst_income = df[df['loan_status']==0]['installment_income_ratio'].median()*100
    
    print(f"  • 收入-借款比：高风险{risk_income_loan:.1f} | 正常{normal_income_loan:.1f}（差值{normal_income_loan-risk_income_loan:.1f}）")
    print(f"  • 月供-收入比：高风险{risk_inst_income:.1f}% | 正常{normal_inst_income:.1f}%（差值{normal_inst_income-risk_inst_income:.1f}%）")
    
    # 4.3 高风险行为占比对比
    print("\n【高风险行为占比对比】")
    high_risk_behavior_cols = [
        ("联合申请", "is_joint_app"), 
        ("现金放款", "is_cash_disburse"),
        ("收入未验证", "is_income_unverified"), 
        ("债务清算", "has_debt_settlement"),
        ("短信用历史", "is_short_credit"), 
        ("批量邮编申请", "is_batch_zip")
    ]
    
    for behavior_name, col in high_risk_behavior_cols:
        risk_rate = df[df['loan_status']==1][col].mean() * 100
        normal_rate = df[df['loan_status']==0][col].mean() * 100
        print(f"  • {behavior_name:8}：高风险{risk_rate:.1f}% | 正常{normal_rate:.1f}%（差异{risk_rate-normal_rate:.1f}个百分点）")
    
    # 4.4 可视化图表生成（统一风格，提升可读性）
    print("\n【可视化图表生成】")
    fig, axes = plt.subplots(2, 2, figsize=(14, 10))
    fig.suptitle("反欺诈核心风险洞察图表", fontsize=14, fontweight="bold", y=0.98)
    
    # 统一图表样式参数
    style_params = {
        "title": {"fontsize": 11, "fontweight": "bold", "pad": 8},
        "label": {"fontsize": 9, "labelpad": 4},
        "tick": {"labelsize": 8},
        "legend": {"fontsize": 8, "loc": "best"}
    }
    
    # 子图1：信用等级与高风险率
    grade_risk_data = df.groupby("grade")["loan_status"].mean() * 100
    grade_risk_data.plot(kind="bar", ax=axes[0,0], color="#ff6b6b", alpha=0.8)
    axes[0,0].set_title("信用等级与高风险率", **style_params["title"])
    axes[0,0].set_xlabel("信用等级（A→G风险递增）", **style_params["label"])
    axes[0,0].set_ylabel("高风险率（%）", **style_params["label"])
    axes[0,0].tick_params(**style_params["tick"])
    axes[0,0].grid(alpha=0.3, axis="y")
    
    # 子图2：收入-借款比分布（风险对比，限制范围0-10）
    risk_income_data = df[df['loan_status']==1]['income_loan_ratio'].clip(0, 10)
    normal_income_data = df[df['loan_status']==0]['income_loan_ratio'].clip(0, 10)
    risk_income_data.plot(kind="hist", ax=axes[0,1], alpha=0.7, label="高风险", bins=20, color="#ff6b6b")
    normal_income_data.plot(kind="hist", ax=axes[0,1], alpha=0.7, label="正常", bins=20, color="#4ecdc4")
    axes[0,1].set_title("收入-借款比分布（≤10）", **style_params["title"])
    axes[0,1].set_xlabel("收入-借款比", **style_params["label"])
    axes[0,1].legend(**style_params["legend"])
    axes[0,1].tick_params(**style_params["tick"])
    axes[0,1].grid(alpha=0.3, axis="y")
    
    # 子图3：月供-收入比与高风险率
    inst_bins = [0, 10, 20, 30, 40, 100]
    inst_risk_data = df.groupby(pd.cut(df["installment_income_ratio"]*100, inst_bins))["loan_status"].mean() * 100
    inst_risk_data.plot(kind="line", ax=axes[1,0], marker="o", color="#ff6b6b", linewidth=2, markersize=5)
    axes[1,0].set_title("月供-收入比与高风险率", **style_params["title"])
    axes[1,0].set_xlabel("月供-收入比（%）", **style_params["label"])
    axes[1,0].set_ylabel("高风险率（%）", **style_params["label"])
    axes[1,0].grid(alpha=0.3)
    axes[1,0].tick_params(**style_params["tick"])
    
    # 子图4：Top10高风险州分布
    state_risk_top10 = df.groupby("addr_state")["loan_status"].mean().sort_values(ascending=False).head(10)
    state_risk_top10.plot(kind="bar", ax=axes[1,1], color="#4ecdc4", alpha=0.8)
    axes[1,1].set_title("Top10高风险州（风险率）", **style_params["title"])
    axes[1,1].set_xlabel("州", **style_params["label"])
    axes[1,1].set_ylabel("高风险率（%）", **style_params["label"])
    axes[1,1].tick_params(axis="x", rotation=45, **style_params["tick"])
    axes[1,1].grid(alpha=0.3, axis="y")
    
    # 保存图表（高清格式，适配作品集）
    plt.tight_layout()
    fig_path = f"{Config.OUTPUT['fig']}/anti_fraud_eda.png"
    plt.savefig(fig_path, dpi=300, bbox_inches="tight", format="png", facecolor="white")
    print(f"  • 图表保存完成：{fig_path}（300dpi高清PNG）")
    
    # 4.5 保存处理后的数据（用于后续建模）
    data_path = f"{Config.OUTPUT['data']}/processed_anti_fraud_data.csv"
    df.to_csv(data_path, index=False, encoding="utf-8-sig")
    
    # 统计字段类型数量
    original_cols = [col for col in df.columns if not (
        col.startswith("purpose_") or col.endswith("_encoded") or 
        col.startswith("is_") or col.startswith("has_") or
        col in ["fico_score", "income_loan_ratio", "installment_income_ratio", "credit_history_years"]
    )]
    feature_cols_count = df.shape[1] - len(original_cols)
    print(f"  • 数据保存完成：{data_path}")
    print(f"  • 数据详情：{len(original_cols)}个原始字段 + {feature_cols_count}个反欺诈特征 = 共{df.shape[1]}个字段")

# 执行EDA分析
fraud_eda_analysis(df_final)

# 全流程总结（规范日志，清晰收尾）
print("\n" + "="*60)
print("🎉 反欺诈数据预处理全流程完成！")
print("="*60)
print("📁 输出文件汇总：")
print(f"  1. 可视化图表：{Config.OUTPUT['fig']}/anti_fraud_eda.png")
print(f"  2. 建模数据：{Config.OUTPUT['data']}/processed_anti_fraud_data.csv")
print("\n💡 后续步骤：将processed_anti_fraud_data.csv导入建模脚本，进行规则验证和模型训练")

